//: C20:BankTeller.cpp
// From Thinking in C++, 2nd Edition
// Available at http://www.BruceEckel.com
// (c) Bruce Eckel 1999
// Copyright notice in Copyright.txt
// Using a queue and simulated multithreading
// To model a bank teller system
#include <iostream>
#include <queue>
#include <list>
#include <cstdlib>
#include <ctime>
using namespace std;

class Customer
{
    int serviceTime;
public:
    Customer() : serviceTime(0) {}
    Customer(int tm) : serviceTime(tm) {}
    int getTime() {
        return serviceTime;
    }
    void setTime(int newtime) {
        serviceTime = newtime;
    }
    friend ostream&
    operator<<(ostream& os, const Customer& c) {
        return os << '[' << c.serviceTime << ']';
    }
};

class Teller
{
    queue<Customer>& customers;
    Customer current;
    static const int slice = 5;
    int ttime; // Time left in slice
    bool busy; // Is teller serving a customer?
public:
    Teller(queue<Customer>& cq)
            : customers(cq), ttime(0), busy(false) {}
    Teller& operator=(const Teller& rv) {
        customers = rv.customers;
        current = rv.current;
        ttime = rv.ttime;
        busy = rv.busy;
        return *this;
    }
    bool isBusy() {
        return busy;
    }
    void run(bool recursion = false) {
        if (!recursion)
            ttime = slice;
        int servtime = current.getTime();
        if (servtime > ttime) {
            servtime -= ttime;
            current.setTime(servtime);
            busy = true; // Still working on current
            return;
        }
        if (servtime < ttime) {
            ttime -= servtime;
            if (!customers.empty()) {
                current = customers.front();
                customers.pop(); // Remove it
                busy = true;
                run(true); // Recurse
            }
            return;
        }
        if (servtime == ttime) {
            // Done with current, set to empty:
            current = Customer(0);
            busy = false;
            return; // No more time in this slice
        }
    }
};

// Inherit to access protected implementation:
class CustomerQ : public queue<Customer>
{
public:
    friend ostream&
    operator<<(ostream& os, const CustomerQ& cd) {
        copy(cd.c.begin(), cd.c.end(),
             ostream_iterator<Customer>(os, ""));
        return os;
    }
};

int main()
{
    CustomerQ customers;
    list<Teller> tellers;
    typedef list<Teller>::iterator TellIt;
    tellers.push_back(Teller(customers));
    srand(time(0)); // Seed random number generator
    while (true) {
        // Add a random number of customers to the
        // queue, with random service times:
        for (int i = 0; i < rand() % 5; i++)
            customers.push(Customer(rand() % 15 + 1));
        cout << '{' << tellers.size() << '}'
             << customers << endl;
        // Have the tellers service the queue:
        for (TellIt i = tellers.begin();
                i != tellers.end(); i++)
            (*i).run();
        cout << '{' << tellers.size() << '}'
             << customers << endl;
        // If line is too long, add another teller:
        if (customers.size() / tellers.size() > 2)
            tellers.push_back(Teller(customers));
        // If line is short enough, remove a teller:
        if (tellers.size() > 1 &&
                customers.size() / tellers.size() < 2)
            for (TellIt i = tellers.begin();
                    i != tellers.end(); i++)
                if (!(*i).isBusy()) {
                    tellers.erase(i);
                    break; // Out of for loop
                }
    }
} ///:~
